package io.israel.gad;

import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.function.Supplier;

@Slf4j
@SpringBootApplication
public class IsraelGadApplication {

	public static void main(String[] args) {
		SpringApplication.run(IsraelGadApplication.class, args);
	}

	@Bean
	public InitializingBean initializingBean() {
		final String templateContent
				= "<p>{{#isoDateFormat}}{{startDate}}{{/isoDateFormat}}</p> <p> {{endDate}}</p>"
				+ "<p> {{#CNY}}{{amount}}{{/CNY}}</p> <p>{{amount}}</p>"
				+ "<p> {{#iso8601MillisTimeZoneFormat}}{{startDate}}{{/iso8601MillisTimeZoneFormat}}</p>";
		ImmutableMap<String, Object> dataObject = new ImmutableMap.Builder<String, Object>()
				.put("startDate", new Date())
				.put("endDate", DateTime.now().plusDays(10).toDate())
				.put("amount", new BigDecimal("10003210.000000"))
				// 对 {{#iso8601DateFormat}} 包围的日期进行重新格式化
				.put(Iso8601DateFormatLambda.INSTANCE.defineTag(), Iso8601DateFormatLambda.INSTANCE)
				// 对 {{#yearMonthFormat}} 包围的日期执行重新格式化
				.put(YearMonthFormatLambda.INSTANCE.defineTag(), YearMonthFormatLambda.INSTANCE)
				// 对 {{#iso8601MillisFormat}} 包围的日期执行重新格式化
				.put(Iso8601MillisFormatLambda.INSTANCE.defineTag(), Iso8601MillisFormatLambda.INSTANCE)
				// 对 {{#iso8601MillisTimeZoneFormat}} 包围的日期执行重新格式化
				.put(Iso8601MillisTimeZoneFormatLambda.INSTANCE.defineTag(), Iso8601MillisTimeZoneFormatLambda.INSTANCE)
				.put("CNY", ChineseCurrencyLambda.INSTANCE)
				.build();

		return () -> {
			String s = Mustache.compiler()
					.nullValue(EmptyStringForNull.INSTANCE.get())
					.withFormatter(DefaultDateTimeFormatter.INSTANCE)
					.compile(templateContent)
					.execute(dataObject);
			log.info("Response String : {}", s);
		};
	}
}


/**
 * @author lijinting01
 */
class EmptyStringForNull implements Supplier<String> {

	public static final EmptyStringForNull INSTANCE = new EmptyStringForNull();

	@Override
	public String get() {
		return StringUtils.EMPTY;
	}
}

/**
 * 默认使用的日期格式
 *
 * @author lijinting01
 */
class DefaultDateTimeFormatter implements Mustache.Formatter {

	private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
	private final DateTimeFormatter formatter = DateTimeFormat.forPattern(DEFAULT_DATE_TIME_FORMAT);

	static final DefaultDateTimeFormatter INSTANCE = new DefaultDateTimeFormatter();

	@Override
	public String format(Object value) {
		if (value instanceof Date) {
			return formatter.print(((Date) value).getTime());
		}
		return String.valueOf(value);
	}
}

/**
 * 对默认日期执行重新格式化的Lambda
 *
 * @author lijinting01
 */
abstract class DateTimeFormatLambda implements Mustache.Lambda {

	/**
	 * 默认使用的日期格式，同 {@link DefaultDateTimeFormatter#DEFAULT_DATE_TIME_FORMAT}
	 */
	private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

	@Override
	public void execute(Template.Fragment frag, Writer out) throws IOException {
		DateTime dateTime = DATE_TIME_FORMATTER.parseDateTime(frag.execute());
		String newDateTime = defineNewFormatter().print(dateTime);
		out.write(newDateTime);
	}

	protected abstract DateTimeFormatter defineNewFormatter();

	/**
	 * 定义Lambda的标签
	 *
	 * @return
	 */
	public abstract String defineTag();
}

/**
 * @author lijinting01
 */
class Iso8601MillisFormatLambda extends DateTimeFormatLambda {
	private static final DateTimeFormatter ISO_DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");

	static final Iso8601MillisFormatLambda INSTANCE = new Iso8601MillisFormatLambda();

	@Override
	protected DateTimeFormatter defineNewFormatter() {
		return ISO_DATE_FORMATTER;
	}

	@Override
	public String defineTag() {
		return "iso8601MillisFormat";
	}
}

/**
 * @author lijinting01
 */
class Iso8601MillisTimeZoneFormatLambda extends DateTimeFormatLambda {
	private static final DateTimeFormatter ISO_DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS zzz");

	static final Iso8601MillisTimeZoneFormatLambda INSTANCE = new Iso8601MillisTimeZoneFormatLambda();

	@Override
	protected DateTimeFormatter defineNewFormatter() {
		return ISO_DATE_FORMATTER;
	}

	@Override
	public String defineTag() {
		return "iso8601MillisTimeZoneFormat";
	}
}

/**
 * ISO 8601 日期格式化
 *
 * @author lijinting01
 */
class Iso8601DateFormatLambda extends DateTimeFormatLambda {


	private static final DateTimeFormatter ISO_DATE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");

	static final Iso8601DateFormatLambda INSTANCE = new Iso8601DateFormatLambda();

	@Override
	protected DateTimeFormatter defineNewFormatter() {
		return ISO_DATE_FORMATTER;
	}

	@Override
	public String defineTag() {
		return "iso8601DateFormat";
	}
}

/**
 * @author lijinting01
 */
class YearMonthFormatLambda extends DateTimeFormatLambda {

	private static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormat.forPattern("yyyy-MM");

	static final YearMonthFormatLambda INSTANCE = new YearMonthFormatLambda();

	@Override
	protected DateTimeFormatter defineNewFormatter() {
		return YEAR_MONTH_FORMATTER;
	}

	@Override
	public String defineTag() {
		return "yearMonthFormat";
	}
}

class ChineseCurrencyLambda implements Mustache.Lambda {
	private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###.00");
	static final ChineseCurrencyLambda INSTANCE = new ChineseCurrencyLambda();

	@Override
	public void execute(Template.Fragment frag, Writer out) throws IOException {
		String currency = frag.execute();
		BigDecimal newBigDecimal = new BigDecimal(currency);
		out.write(DECIMAL_FORMAT.format(newBigDecimal));
	}
}